
package com.idealighter.game.games.tongbiniuniu.manager;

import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import com.idealighter.game.common.CommonMsgMgr;
import com.idealighter.game.core.common.Game;
import com.idealighter.game.core.constant.Operator;
import com.idealighter.game.core.error.ErrorCode;
import com.idealighter.game.dblog.service.DbLogService;
import com.idealighter.game.dictionary.dic.TongbiniuniuRoomDic;
import com.idealighter.game.dictionary.dic.TongbiniuniuTimeDic;
import com.idealighter.game.dictionary.domain.TongbiniuniuRoomDomain;
import com.idealighter.game.event.manager.EventMgr;
import com.idealighter.game.event.struct.ClearGameDataEvent;
import com.idealighter.game.event.struct.PlayerExitEvent;
import com.idealighter.game.event.struct.ShutdownRoomEvent;
import com.idealighter.game.event.struct.StartRoomEvent;
import com.idealighter.game.gamehall.event.EnterRoomEvent;
import com.idealighter.game.gamehall.event.EnterTableEvent;
import com.idealighter.game.gamehall.event.ExitRoomEvent;
import com.idealighter.game.gamehall.event.ExitTableEvent;
import com.idealighter.game.gamehall.event.RoomMemInfoUpdateEvent;
import com.idealighter.game.gamehall.manager.GameHallMgr;
import com.idealighter.game.games.common.AbstractRoom;
import com.idealighter.game.games.common.SeatState;
import com.idealighter.game.games.common.niuniu.NiuNiuCard;
import com.idealighter.game.games.common.niuniu.NiuNiuCardsType;
import com.idealighter.game.games.tongbiniuniu.dto.BillInfo;
import com.idealighter.game.games.tongbiniuniu.struct.TongBiNiuNiuRoom;
import com.idealighter.game.games.tongbiniuniu.struct.TongBiNiuNiuSeat;
import com.idealighter.game.games.tongbiniuniu.struct.TongBiNiuNiuTable;
import com.idealighter.game.log.core.LogReason;
import com.idealighter.game.log.struct.game.Game14WinLoseLog;
import com.idealighter.game.log.struct.game.GameAfeeLog;
import com.idealighter.game.login.manager.LoginMgr;
import com.idealighter.game.player.manager.PlayerMgr;
import com.idealighter.game.player.service.bo.PlayerBo;
import com.idealighter.game.player.struct.Player;
import com.idealighter.game.schedule.manager.ScheduleMgr;
import com.idealighter.game.server.ExecutorMgr;
import com.idealighter.game.server.core.executor.DisruptorExecutor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * . 通比牛牛逻辑处理 .
 * 
 * @date 2016年2月17日 上午10:46:55
 */
@Singleton
public class TongBiNiuNiuMgr {

  private static final Logger LOG = LoggerFactory.getLogger(TongBiNiuNiuMgr.class);

  // 玩家手上的牌数
  public static final int PLAYER_CARDS = 5;

  // 通比牛牛定时服务器增加的延时，因为与网络延时等原因
  private static final int ADDED_DELAY = 5;

  private final EventMgr eventMgr;
  // 游戏线程executor,可以执行普通任务和定时任务
  private final DisruptorExecutor gameExecutor;

  @Inject

  private TongbiniuniuTimeDic timeDic;
  @Inject
  private TongBiNiuNiuControlScript niuniuControl;
  @Inject
  private PlayerMgr playerMgr;
  @Inject
  private ScheduleMgr scheduleMgr;
  @Inject
  private CommonMsgMgr commonMsgMgr;
  @Inject
  private TongBiNiuNiuMsgMgr msgMgr;
  @Inject
  private TongBiNiuNiuDataMgr dataMgr;
  @Inject
  private TongbiniuniuRoomDic roomDic;
  @Inject
  private GameHallMgr gameHallMgr;
  @Inject
  private LoginMgr loginMgr;

  /**
   * 构造函数 .
   * 
   * @param eventMgr .
   * @param executorMgr .
   */
  @Inject
  public TongBiNiuNiuMgr(EventMgr eventMgr, ExecutorMgr executorMgr) {
    this.eventMgr = eventMgr;
    eventMgr.register(this);
    this.gameExecutor = executorMgr.getGameExecutor(Game.TONGBI_NIUNIU.getModuleId());
  }

  /**
   * 订阅玩家退出游戏事件, 在gameExecutor中执行, 避免线程安全问题.
   * 
   * @param event .
   */
  @Subscribe
  public void onPlayerExitGame(PlayerExitEvent event) {
    gameExecutor.execute(() -> {
      Player player = event.player;
      long playerId = player.getId();

      TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(playerId);
      if (seat != null) {
        // 玩家在房间中但是没有准备，退出桌子，退出房间
        doExitTabl(seat);
      }

      // 玩家是否在牛牛房间中
      if (player.curRoom instanceof TongBiNiuNiuRoom) {
        // 退出房间
        exitRoom(player);
      }
    });
  }

  /**
   * 订阅开启房间事件.
   * 
   * @param event .
   */
  @Subscribe
  public void onStartRoom(StartRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.TONGBI_NIUNIU) {
        roomDic.load();
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onShutDownRoom(ShutdownRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.TONGBI_NIUNIU) {
        roomDic.load();
        AbstractRoom room = dataMgr.getRoom(event.getRoomId());
        for (Long playerId : room.getPlayers()) {
          Player player = playerMgr.getPlayer(playerId);
          if (player != null) {
            loginMgr.noticeLogoutForGameExit(player);
          }
        }
      }

    });
  }

  @Subscribe
  public void onClearGameData(ClearGameDataEvent event) {
    gameExecutor.execute(() -> doClearGameData(event.playerId));
  }

  /**
   * 进入游戏大厅 .
   * 
   * @param player 玩家.
   */
  public void enterGameHall(Player player) {
    if (player.curRoom != null) {
      Game game = player.curRoom.game();
      LOG.warn("[通比牛牛]玩家[{}][{}]在[{}]游戏中，暂时无法进入游戏", player.getId(), player.getPlayerName(),
          game.getDesc());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    msgMgr.sendEnterGameHallMsg(player);
    LOG.info("[通比牛牛]玩家[{}][{}]进入大厅成功", player.getPlayerName(), player.getId());
  }

  /**
   * 玩家进入房间 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void enterRoom(Player player, int roomId) {
    AbstractRoom curRoom = player.curRoom;
    if (curRoom != null) {
      Game game = curRoom.game();
      LOG.warn("[通比牛牛]玩家[{}][{}]当前在游戏[{}]房间[{}]中不能进入房间", player.getId(), player.getPlayerName(),
          curRoom.getId());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    TongBiNiuNiuRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    TongbiniuniuRoomDomain roomDom = roomDic.get(roomId);
    if (roomDom.getIsActive() != 1) {
      LOG.error("[通比牛牛]玩家[{}][{}]进入的房间[{}]已经关闭", player.getPlayerName(), player.getId(), roomId);
      return;
    }
    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getLower());
      return;
    }
    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getUpper());
      return;
    }

    if (!player.vip()
        && room.getPlayers().size() >= roomDom.getMaxNum() * roomDom.getOrdinarPeople() / 100) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_VIP_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的房间[{}]普通玩家人数已超过上限", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      return;
    }

    if (room.getPlayers().size() >= roomDom.getMaxNum()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN_MATCH, Operator.SYSTEM);
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的房间[{}]已超过最大人数[{}]", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      return;
    }

    // 修改玩家当前房间
    player.curRoom = room;
    room.getPlayers().add(player.getId());
    // 发送进入房间消息
    msgMgr.sendEnterRoomMsg(player);
    LOG.info("[通比牛牛]玩家[{}][{}]进入房间[{}]成功", player.getPlayerName(), player.getId(), roomId);

    // 触发进入房间事件
    eventMgr.post(new EnterRoomEvent(player, room));
  }

  /**
   * 兑换筹码的金币 玩家金币兑换筹码 .
   * 
   * @param player 玩家.
   * @param gold .
   */
  public void exchangeChips(Player player, long gold) {
    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]不在牌桌中不能兑换筹码", player.getPlayerName(), player.getId());
      return;
    }

    if (player.getGold() < gold
        + convertGold(seat.table.room.getId().intValue(), seat.getTotalChips())) {
      LOG.error("[通比牛牛]玩家[{}][{}]兑换的金币[{}]加上筹码[{}]兑换的金币超过自己的金币总和[{}]", player.getPlayerName(),
          player.getId(), gold, seat.getTotalChips(), player.getGold());
      return;
    }

    if (seat.readied()) {
      LOG.error("[通比牛牛]玩家[{}][{}]已经开始游戏不能兑换筹码", player.getPlayerName(), player.getId());
      return;
    }

    long chips = convertChip(seat.table.room.getId().intValue(), gold);
    seat.setTotalChips(seat.getTotalChips() + chips);
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);

    LOG.info("[通比牛牛]玩家[{}][{}]用金币[{}]兑换筹码[{}]成功", player.getPlayerName(), player.getId(), gold,
        chips);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家筹码兑换金币 .
   * 
   * @param player 玩家.
   * @param chips .
   */
  public void exchangeGold(Player player, long chips) {
    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]不在牌桌中不能兑换金币", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.getTotalChips() < chips) {
      LOG.error("[通比牛牛]玩家[{}][{}]兑换的筹码[{}]超过自己的筹码总和[{}]", player.getPlayerName(), player.getId(),
          chips, seat.getTotalChips());
      return;
    }

    if (seat.readied()) {
      LOG.error("[通比牛牛]玩家[{}][{}]已经开始游戏不能兑换金币", player.getPlayerName(), player.getId());
      return;
    }

    seat.setTotalChips(seat.getTotalChips() - chips);
    long gold = convertGold(seat.table.room.getId().intValue(), chips);

    // 发送兑换筹码变更
    msgMgr.sendChipsChangeMsg(seat);

    LOG.info("[通比牛牛]玩家[{}][{}]用筹码[{}]兑换金币[{}]成功", player.getPlayerName(), player.getId(), chips,
        gold);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家退出房间 .
   * 
   * @param player 玩家.
   */
  public void exitRoom(Player player) {
    AbstractRoom curRoom = player.curRoom;
    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());

    if (curRoom instanceof TongBiNiuNiuRoom) {
      if (seat == null) {
        player.curRoom = null;
        TongBiNiuNiuRoom room = (TongBiNiuNiuRoom) curRoom;
        room.getPlayers().remove(player.getId());
        msgMgr.sendExitRoomMsg(player, 0);

        LOG.info("[通比牛牛]玩家[{}][{}]退出房间[{}]", player.getPlayerName(), player.getId(), room.getId());
        // 触发退出房间事件
        eventMgr.post(new ExitRoomEvent(player, curRoom));
      } else {
        LOG.error("[通比牛牛]玩家[{}][{}]在座位中不能退出房间,请先退出桌子", player.getPlayerName(), player.getId());
        msgMgr.sendExitRoomMsg(player, 1);
      }
    } else {
      LOG.warn("[通比牛牛]玩家[{}][{}]不在通比牛牛房间中，无法退出房间", player.getPlayerName(), player.getId());
    }
  }

  /**
   * 筹码换算金币,只会是多个金币等于一个筹码 .
   * 
   * @param roomId .
   * @param chips .
   */
  public long convertGold(int roomId, long chips) {
    TongbiniuniuRoomDomain roomDomain = roomDic.get(roomId);

    return chips * roomDomain.getProportionGold() / roomDomain.getProportionChips();
  }

  /**
   * 金币换算筹码 .
   * 
   * @param roomId .
   * @param gold .
   * @return
   */
  public long convertChip(int roomId, long gold) {
    TongbiniuniuRoomDomain roomDomain = roomDic.map().get(roomId);

    return gold * roomDomain.getProportionChips() / roomDomain.getProportionGold();
  }

  /**
   * 玩家进入牌桌 .
   * 
   * @param player 玩家.
   * @param tableId .
   * @param order .
   */
  public void enterTable(Player player, int tableId, byte order, String tablePwd) {
    if (!(player.curRoom instanceof TongBiNiuNiuRoom)) {
      LOG.error("[通比牛牛]玩家[{}][{}]当前不在房间中不能进入通比牛牛桌子", player.getPlayerName(), player.getId());
      return;
    }

    TongBiNiuNiuTable table = dataMgr.getTable(tableId);

    if (table == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]进入的牌桌[{}]不存在", player.getPlayerName(), player.getId(), tableId);
      return;
    }

    if (dataMgr.getPlayerSeat(player.getId()) != null) {
      LOG.error("[通比牛牛]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    TongBiNiuNiuSeat seat = table.getSeat(order);
    if (seat == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]进入的牌桌位置[{}]不合法", player.getPlayerName(), player.getId(), order);
      return;
    }

    if (seat.getPlayerId() > 0) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_SET_NOT_EMPTY, Operator.SYSTEM,
          seat.getPlayerName());
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的牌桌位置[{}]已经有人", player.getPlayerName(), player.getId(), order);
      return;
    }

    // 进入座位
    doEnterTable(player, seat, tablePwd);
  }

  /**
   * 快速进入四人牛牛 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void fastEnterTable(Player player, int roomId) {
    if (!(player.curRoom instanceof TongBiNiuNiuRoom)) {
      LOG.error("[通比牛牛]玩家[{}][{}]当前不在房间中不能直接进入桌子", player.getPlayerName(), player.getId());
      return;
    }

    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat != null) {
      LOG.error("[通比牛牛]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    TongBiNiuNiuRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]快速进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    TongBiNiuNiuSeat emptySeat = findEmptySeat(player, room);
    if (emptySeat != null) {
      doEnterTable(player, emptySeat, null);
    } else {
      // 发送快速进入桌子结果
      msgMgr.sendFastEnterTableMsg(player, 1);
      commonMsgMgr.sendTipDlgMsg(player, ErrorCode.GAME_NOT_SIT, Operator.SYSTEM);
    }
  }

  /**
   * 玩家进入座位 .
   * 
   * @param player 玩家.
   * @param seat 座位信息.
   */
  private void doEnterTable(Player player, TongBiNiuNiuSeat seat, String tablePwd) {
    int roomId = seat.table.room.getId().intValue();
    TongBiNiuNiuTable table = seat.table;

    if (table.readied) {
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的桌子[{}]已经开始游戏", player.getPlayerName(), player.getId(),
          table.getId());
      return;
    }

    TongbiniuniuRoomDomain roomDom = roomDic.get(seat.table.room.getId().intValue());
    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getLower());
      return;
    }

    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[通比牛牛]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getUpper());
      return;
    }

    // 玩家进桌前的房间设置校验
    if (!gameHallMgr.checkTableSetting(player, seat.table(), tablePwd)) {
      return;
    }

    if (player.getGold() < roomDom.getAfee()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_ENOUGH_FEE, Operator.SYSTEM);
      LOG.warn("[通比牛牛]玩家[{}][{}]进入通比牛牛桌子金币不足台费", player.getPlayerName(), player.getId());
      return;
    } else {
      playerMgr.minusGold(player, roomDom.getAfee(),
          LogReason.TONGBI_NIUNIU_ENTER_TABLE_MINUS_AFEE);
      DbLogService.log(new GameAfeeLog(player, table, roomDom.getAfee()));
    }

    seat.setPlayerId(player.getId());
    seat.setPlayerName(player.getPlayerName());
    seat.setSex(player.getSex());
    seat.setRobot(player.robot());
    seat.setState(SeatState.SEATED);

    // 更新玩家座位
    dataMgr.updatePlayerSeats(player.getId(), seat);
    // 触发进入桌子事件
    eventMgr.post(new EnterTableEvent(player, seat));

    // 向玩家发送进入牌桌结果信息
    msgMgr.sendEnterTableMsg(player, table);
    msgMgr.sendOtherEnterTableMsg(seat);
    LOG.info("[通比牛牛]玩家[{}][{}]进入桌子[{}]座位[{}]成功", player.getPlayerName(), player.getId(),
        table.getId(), seat.getOrder());

    int readyTime = timeDic.map().get(1).getTime();
    // 进入座位后如果没有在指定时间内准备好，则退出座子
    seat.stepFuture = scheduleMgr.schedule(() -> doExitTabl(seat), readyTime + ADDED_DELAY,
        TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 玩家退出牌桌 .
   * 
   * @param player 玩家.
   */
  public void exitTable(Player player) {
    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]没在牌桌中不能退出牌桌", player.getPlayerName(), player.getId());
      return;
    }

    // 退出桌子
    doExitTabl(seat);
  }

  /**
   * 退出桌子 .
   * <p/>
   * 1.玩家正常退出牌桌, 能在在线玩家中拿到player, 正常退出桌子
   * <p/>
   * 2.玩家非正常退出(如掉线)后,从活跃玩家或数据库中拿到PlayerDomain，结算后马上更新缓冲和数据库
   * 
   * @param seat 座位信息.
   */
  private void doExitTabl(TongBiNiuNiuSeat seat) {
    long playerId = seat.getPlayerId();
    final Player player = playerMgr.getPlayer(playerId);
    TongBiNiuNiuTable table = seat.table;

    /*
     * 退桌处理 . 1.发牌前，任何人退都重新开始 2.发牌后，闲家退闲家摊牌结算，庄家退，都摊牌结算
     */
    if (seat.cards.size() == 0) { // 发牌前
      if (table.readied) { // 都准备好
        // 游戏结束后没有在指定时间内准备好，则退出座子
        int readyTime = timeDic.map().get(1).getTime();
        for (TongBiNiuNiuSeat s : table.seats) {
          if (s.getPlayerId() > 0) {
            s.resetButTotalChips();
            s.stepFuture = scheduleMgr.schedule(() -> doExitTabl(s), readyTime + ADDED_DELAY,
                TimeUnit.SECONDS, gameExecutor);
            eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(s.getPlayerId())));
          }
        }
      }
    } else { // 发牌后
      if (!seat.showdown) { // 已经发牌,没有摊牌则摊牌并结算
        doShowdown(seat, true);
      }
      if (seat.billChips != 0) {
        doBill(seat);
      }
    }

    // 发送退出房间消息
    msgMgr.sendExitTableMsg(seat);
    dataMgr.removePlayerSeat(seat.getPlayerId());
    LOG.info("[通比牛牛]玩家[{}][{}]退出桌子[{}]", seat.getPlayerId(), seat.getPlayerName(),
        seat.table.getId());
    seat.clear();

    // 3个人在桌子中，2个人准备，剩下那个人没有准备则准备结束
    if (!table.readied && readiedOver(table)) { // 都准备完成,对所有玩家进行游戏过程定时(没有对每个步骤定时)
      readyOver(table);
      playerBet(table);
    }

    // 触发退出桌子事件
    eventMgr.post(new ExitTableEvent(player, seat));
  }

  /**
   * 玩家准备 .
   * 
   * @param player 玩家.
   */
  public void ready(Player player) {
    // 座位
    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());

    if (seat == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]还未进入牌桌不能准备", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.readied()) {
      LOG.warn("[通比牛牛]玩家[{}][{}]已经准备好,请勿重复准备", player.getPlayerName(), player.getId());
      msgMgr.sendReadyMsg(seat, 1);
      return;
    }

    TongBiNiuNiuTable table = seat.table;
    int roomId = table.room.getId().intValue();
    TongbiniuniuRoomDomain roomDomain = roomDic.get(roomId);

    // 校验玩家身上的钱换成的筹码是否足够(最小筹码*8 * 3) .
    if (seat.getTotalChips() < roomDomain.getMinOne() * 8) {
      LOG.warn("[通比牛牛]玩家[{}][{}]玩家身上的筹码不足[{}(最小筹码*8 * 3)]不能准备", player.getPlayerName(),
          player.getId(), roomDomain.getMinOne() * 8);
      return;
    }
    // if (seat.totalChips < 1000) {
    // LOG.warn("玩家[{}][{}]玩家身上的筹码不足下注筹码不能准备");
    // return;
    // }

    seat.setState(SeatState.READY);
    // 清除准备计时
    seat.stepFuture.cancel(false);

    msgMgr.sendReadyMsg(seat, 0);
    LOG.info("[通比牛牛]玩家[{}][{}]准备好", player.getPlayerName(), player.getId());

    // 向同桌的玩家发送该座位的玩家已经准备好
    eventMgr.post(new RoomMemInfoUpdateEvent(player));

    /*
     * 计算桌子是否准备好 .
     */
    if (readiedOver(table)) { // 都准备完成,对所有玩家进行游戏过程定时(没有对每个步骤定时)
      readyOver(table);
      playerBet(table);
    }
  }

  /**
   * 桌子是否准备好,会改变桌子readied值 .
   * 
   * @param table .
   * @return 返回桌子是否准备好
   */
  private boolean readiedOver(TongBiNiuNiuTable table) {
    boolean tableReadied = true;
    // 准备好的玩家
    int readiedNum = 0;
    for (TongBiNiuNiuSeat s : table.seats) {
      if (s.getPlayerId() > 0) {
        if (!s.readied()) {
          tableReadied = false;
          break;
        }
        readiedNum++;
      }
    }
    if (readiedNum <= 1) { // 必须大于1人才算准备好
      tableReadied = false;
    }

    table.readied = tableReadied;
    return tableReadied;
  }

  /**
   * 桌子全部准备完成 .
   * 
   * @param table .
   */
  private void readyOver(TongBiNiuNiuTable table) {
    // 都准备完成后对游戏过程(叫庄、下注、摊牌)定时,防止玩家卡在游戏中,时间到了后如果已经发牌自动结算退出
    int gameTime = timeDic.get(2).getTime() + timeDic.get(3).getTime() + timeDic.get(4).getTime()
        + timeDic.get(5).getTime();
    for (TongBiNiuNiuSeat s : table.seats) {
      if (s.getPlayerId() > 0) {
        s.setState(SeatState.GAMING);
        // 清除准备计时
        s.stepFuture.cancel(false);
        eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(s.getPlayerId())));

        // 触发房间大厅成员信息变更事件(玩家状态变为游戏中需要广播出去)
        s.stepFuture =
            scheduleMgr.schedule(() -> doExitTabl(s), gameTime * 2, TimeUnit.SECONDS, gameExecutor);
      }
    }
    msgMgr.sendReadyOverMsg(table);
  }

  /**
   * 玩家换桌 .
   * 
   * @param player 玩家.
   */
  public void exchangeTable(Player player) {
    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]没在座位中不能换位置", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.readied()) {
      LOG.error("[通比牛牛]玩家[{}][{}]已经准备好不能换桌子", player.getPlayerName(), player.getId());
      msgMgr.sendExchangeTableMsg(player, 1);
      return;
    }

    TongBiNiuNiuRoom room = seat.table.room;

    // 退出桌子
    exitTable(player);
    // 交换到其他桌的位置
    TongBiNiuNiuSeat exchangedSeat = findEmptySeat(player, room);
    if (exchangedSeat == null) {
      LOG.warn("[通比牛牛]当前房间没有空位置给玩家[{}][{}]交换", player.getPlayerName(), player.getId());
      msgMgr.sendExchangeTableMsg(player, 2);
    } else {
      // 进入位置
      doEnterTable(player, exchangedSeat, null);
    }
  }

  /**
   * 查找空位置, 优先查找有人的桌子.
   * 
   * @param player 玩家.
   * @param room .
   * @return
   */
  private TongBiNiuNiuSeat findEmptySeat(Player player, TongBiNiuNiuRoom room) {
    TongBiNiuNiuSeat emptySeat = null;
    ArrayList<TongBiNiuNiuTable> tables = new ArrayList<>(room.tables.values());
    Collections.shuffle(tables);

    for (TongBiNiuNiuTable table : tables) {
      if (table.getPassword() != null) {
        continue;
      }
      if (table.readied) {
        continue;
      }

      if (table.getLimitIp() != null && table.getLimitIp().equals(player.ip)) {
        continue;
      }

      if (player.getGold() < table.getLimitGold()) {
        continue;
      }

      boolean hasPlayer = false;
      TongBiNiuNiuSeat tableEmptySeat = null;

      for (TongBiNiuNiuSeat seat : table.seats) {
        if (seat.getPlayerId() == 0) {
          emptySeat = seat;
          tableEmptySeat = seat;
        } else {
          hasPlayer = true;
        }
      }

      if (hasPlayer && tableEmptySeat != null) {
        return tableEmptySeat;
      }
    }

    return emptySeat;
  }

  /**
   * 玩家下注(准备后自动下注).
   * 
   * @param table .
   */
  public void playerBet(TongBiNiuNiuTable table) {
    TongbiniuniuRoomDomain roomDomain = roomDic.get(table.room.getId().intValue());
    long minOne = roomDomain.getMinOne();
    table.seats.stream().filter(seat -> seat.getPlayerId() > 0).forEach(seat -> {
      seat.betedchips = minOne;
      // seat.totalChips -= seat.betedchips;
      table.totalChips += seat.betedchips;
      // msgMgr.sendChipsChangeMsg(seat);
    });

    // if (table.betOver()) {
    LOG.info("[通比牛牛]玩家下注结束,下注筹码开始洗牌发牌");
    Collections.shuffle(table.cards);

    // 控制牛牛发牌
    niuniuControl.controlDealCards(table);
    // }
  }

  /**
   * 摊牌.
   * 
   * @param player 玩家.
   * @param playerCardIds . 玩家选择的3张牌的组合,如果玩家自己选择的牌是最优的牌则用玩家的 玩家摊牌 .
   */
  public void showdown(Player player, List<Integer> playerCardIds) {
    TongBiNiuNiuSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[通比牛牛]玩家[{}][{}]不在牌桌中不能摊牌", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.showdown) {
      LOG.error("[通比牛牛]玩家[{}][{}]重复摊牌", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.cards.size() == 0) {
      LOG.error("[通比牛牛]牌局还未开始，玩家[{}][{}]不能摊牌", player.getPlayerName(), player.getId());
      return;
    }

    /*
     * 计算玩家自己选择的牌是不是最优的牌 .
     */
    // 玩家选择的3张数字和
    int totalNum = 0;
    // 玩家自定义的最优牌
    List<NiuNiuCard> playerBestCards = new ArrayList<>();
    for (int i = 0; i < playerCardIds.size() && i < 3; i++) {
      NiuNiuCard card = NiuNiuCard.card(playerCardIds.get(i));
      playerBestCards.add(card);
      if (seat.cards.contains(card)) {
        totalNum += card.num;
      } else {
        break;
      }

      // 有大王或者小王totalNum肯定是10的倍数
      if (i == 2 && (totalNum % 10 == 0 || playerBestCards.contains(NiuNiuCard.DA_WANG)
          || playerBestCards.contains(NiuNiuCard.XIAO_WANG))) {
        List<NiuNiuCard> seatCards = new ArrayList<>(seat.cards);
        seatCards.removeAll(playerBestCards);
        // 玩家自定义牌组合的牛类型
        int niu = (seatCards.get(0).num + seatCards.get(1).num) % 10;
        NiuNiuCardsType playerBestCardsType =
            (niu == 0 ? NiuNiuCardsType.geCardsType(10) : NiuNiuCardsType.geCardsType(niu));
        if (seat.cardsType == playerBestCardsType) {
          seat.bestCards.clear();
          seat.bestCards.addAll(playerBestCards);
          seat.bestCards.addAll(seatCards);
        }
      }
    }

    // 摊牌
    doShowdown(seat, false);
  }

  /**
   * 摊牌 .
   * 
   * @param seat 座位信息.
   * @param bill 是否结算 .
   */
  private void doShowdown(TongBiNiuNiuSeat seat, boolean bill) {
    seat.showdown = true;
    msgMgr.sendShowdownMsg(seat);

    TongBiNiuNiuTable table = seat.table;
    LOG.info("[通比牛牛]玩家[{}][{}]摊牌", seat.getPlayerName(), seat.getPlayerId());

    // 所有玩家都摊牌则游戏结束，并且结算
    if (table.showdown()) {
      gameOver(table);
    } else if (bill) {
      doBill(seat);
    }
  }

  /**
   * 该牌桌游戏结束 .
   * 
   * @param table .
   */
  private void gameOver(TongBiNiuNiuTable table) {
    // 结算信息
    List<BillInfo> billInfos = new ArrayList<>();
    for (TongBiNiuNiuSeat seat : table.seats) {
      long playerId = seat.getPlayerId();
      if (playerId > 0) {
        BillInfo billInfo = new BillInfo();
        billInfo.setOrder(seat.getOrder());
        billInfo.setChips(seat.billChips);
        billInfos.add(billInfo);

        // 结算
        doBill(seat);
      }
    }
    table.reset();
    msgMgr.sendGameOver(table, billInfos);
  }

  /**
   * 座位结算 .
   * 
   * @param seat 座位信息.
   */
  private void doBill(TongBiNiuNiuSeat seat) {
    long playerId = seat.getPlayerId();
    int roomId = seat.table.room.getId().intValue();
    // 扣除输赢
    seat.setTotalChips(seat.getTotalChips() + seat.billChips);
    long billGold = convertGold(roomId, seat.billChips);

    /*
     * 游戏结算金币 .
     */
    Player player = playerMgr.getPlayer(playerId);
    PlayerBo playerDom = playerMgr.selectPlayer(playerId);
    if (player != null) {
      playerMgr.addGold(player, billGold, LogReason.TONGBI_NIUNIU_BILL);
    } else {
      playerMgr.addGold(playerDom, billGold, LogReason.TONGBI_NIUNIU_BILL);
    }

    LOG.info("[通比牛牛]]]玩家[{}][{}]结算筹码[{}]",
        player != null ? player.getPlayerName() : playerDom.getPlayerName(),
        player != null ? player.getId() : playerDom.getId(), seat.billChips);
    // 数据库输赢日志
    DbLogService.log(new Game14WinLoseLog(playerDom, seat, seat.billChips, billGold,
        LogReason.TONGBI_NIUNIU_BILL));
    if (seat.billChips > 0) {
      msgMgr.sendedNotice(seat.getPlayerName(), seat.cardsType.niu, seat.billChips);
    }

    seat.resetButTotalChips();

    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);

    int readyTime = timeDic.map().get(1).getTime();
    // 游戏结束后没有在指定时间内准备好，则退出座子
    seat.stepFuture = scheduleMgr.schedule(() -> doExitTabl(seat), readyTime + ADDED_DELAY,
        TimeUnit.SECONDS, gameExecutor);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(playerId)));
  }

  /**
   * 清除玩家,服务器出现bug，玩家卡在游戏中，后台可以清除玩家 .
   * 
   * @param playerId 玩家id.
   */
  private void doClearGameData(long playerId) {
    try {

      dataMgr.removePlayerSeat(playerId);
      /*
       * 清除玩家房间数据 .
       */
      for (TongBiNiuNiuRoom room : dataMgr.rooms()) {
        room.getPlayers().remove(playerId);

        /*
         * 清除房间中玩家所在桌子数据 .
         */
        for (TongBiNiuNiuTable table : room.tables.values()) {

          // 是否在桌子中
          boolean inTable = false;
          for (TongBiNiuNiuSeat s : table.seats) {
            if (s.getPlayerId() > 0 && s.getPlayerId() == playerId) {
              inTable = true;
              break;
            }
          }

          if (inTable) { // 在桌子中清除数据
            for (TongBiNiuNiuSeat s : table.seats) {
              if (s.getPlayerId() > 0) {
                dataMgr.removePlayerSeat(s.getPlayerId());
                room.getPlayers().remove(s.getPlayerId());

                /*
                 * 清除座位数据 .
                 */
                s.clear();
                Player player = playerMgr.getPlayer(s.getPlayerId());
                if (player != null) {
                  loginMgr.noticeLogoutForGameExit(player);
                }
              }
            }
          }
        }
      }

      Player player = playerMgr.getPlayer(playerId);
      if (player != null) {
        loginMgr.noticeLogoutForGameExit(player);
      }
    } catch (Exception e) {
      LOG.error("[通比牛牛]清除玩家[" + playerId + "]卡限失败", e);
    }
  }
}
